package twitter.Database;

import java.io.StringReader;
import java.io.StringWriter;

import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;


import twitter.TwitterLibrary.*;
/****************************************************************************
 * This class is designed for maintaining Twitter Database.
 * The twitter database is a org.w3c.dom.Document type.The format is as follows:
 * <twitts maxVersion=1>
 * 		<twitt version=1>
 * 			<hashtag>san</hashtag>
 * 			<hashtag>job</hashtag>
 * 			<status>#san #job this is a test</status>
 * 		</twitt>
 * </twitts>
 * @author fang
 *
 */
public class TwitterMaintainer {

	private DocumentBuilderFactory factory = null;

	private Document doc;
	private Node twitts;
	private XPathFactory xfactory;
	private XPath xPath;
	private Utility utility;
	private final ReentrantReadWriteLock rwl ;
	private final Lock r;
	private final Lock w;
	static Logger logger = Logger.getLogger(TwitterMaintainer.class);
/******************************************************************************
 * Class constructor
 * @param rwl to ensure the read write lock
 */
	public TwitterMaintainer(ReentrantReadWriteLock rwl,Utility utility) {
		this.rwl=rwl;
		this.r=this.rwl.readLock();
		this.w=this.rwl.writeLock();
		this.utility = utility;
		factory = DocumentBuilderFactory.newInstance();
		BasicConfigurator.configure();

		xfactory = XPathFactory.newInstance();
		xPath = xfactory.newXPath();
		writeInitial("this is the #first twitt");
	}
/**********************************************************
 * for unit test
 * @param args
 */
	public static void main(String[] args) {
		/*TwitterMaintainer tt = new TwitterMaintainer();

		tt.addNewStatus("%23rainy+I+come+%23lalalal");

		System.out.println("ok");
		tt.searchStatusByTag("rainy");
		*/
	}
/***************************************************************
 * build the initial dom
 * @param query
 * @return
 */
	private Document writeInitial(String query) {
		DocumentBuilder db = null;
		try {
			db = factory.newDocumentBuilder();
		} catch (ParserConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		doc = db.newDocument();
		Element DB_twitts = doc.createElement("twitts");
		DB_twitts.setAttribute("maxVersion", "1");
		doc.appendChild(DB_twitts);
		Element DB_twitt = doc.createElement("twitt");
		DB_twitt.setAttribute("version", "1");
		ArrayList<String> hashtags = utility.getHashflag(query);
		for (int i = 0; i < hashtags.size(); i++) {
			Element hashtag = doc.createElement("hashtag");
			hashtag.setTextContent(hashtags.get(i).toString());
			DB_twitt.appendChild(hashtag);
		}
		Element content = doc.createElement("status");
		content.setTextContent(query);

		DB_twitt.appendChild(content);
		DB_twitts.appendChild(DB_twitt);
		return doc;

	}
/*************************************************************
 * Transfre the DOM to InputSource
 * @return InputSource
 */
	private InputSource getInputSourceFromDOC() {
		DOMSource source = new DOMSource(this.doc);
		StringWriter xmlAsWriter = new StringWriter();
		StreamResult result = new StreamResult(xmlAsWriter);
		try {
			TransformerFactory.newInstance().newTransformer().transform(source,
					result);
		} catch (TransformerConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (TransformerException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (TransformerFactoryConfigurationError e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		StringReader xmlReader = new StringReader(xmlAsWriter.toString());
		InputSource resultsource = new InputSource(xmlReader);
		return resultsource;
	}
/*************************************************************
 * Search the max version for the query tag
 * @param tag specify the hashtag
 * @return max version
 */
	public int searchForVersion(String tag) {
		r.lock();
		try {
			logger.info("reading version data from backend server  and the lock information is : "+rwl.toString());
			int result = -1;
			String xPathExpression = "/twitts/twitt[hashtag='" + tag
					+ "']/@version";
			System.out.println(xPathExpression);
			NodeList nodes;

			// xmlDocument =
			// new File("twitts.xml");
			InputSource inputSource = getInputSourceFromDOC();
			// new InputSource(new
			// FileInputStream(xmlDocument));
			nodes = (NodeList) xPath.evaluate(xPathExpression, inputSource,
					XPathConstants.NODESET);

			int maxVersion = -1;
			for (int i = 0; i < nodes.getLength(); i++) {
				Node node = nodes.item(i);
				int version = Integer.parseInt(node.getNodeValue());
				if (version > maxVersion) {
					maxVersion = version;
				}
			}
			result = maxVersion;

			return result;
		} catch (XPathExpressionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			r.unlock();
		}
		return -1;

	}
/*******************************************************************************
 * search status and version by hash tag
 * @param tag specify the hash tag
 * @return a ArrayList object which has all the status and tags in it
 */
	public ArrayList<ArrayList<String>> searchStatusByTag(String tag) {
		r.lock();
		try {
			logger.info("reading status data from backend server and the lock information is : "+rwl.toString());
			ArrayList<ArrayList<String>> result = new ArrayList<ArrayList<String>>();
			ArrayList<String> statusList = new ArrayList<String>();
			ArrayList<String>  versionList = new ArrayList<String>();
			try {
				String xPathExpression = "/twitts/twitt[hashtag='" + tag + "']";
				System.out.println(xPathExpression);
				InputSource inputSource;
				System.out.println("in the get : " + utility.xmlToString(doc));
				NodeList nodes;
				inputSource = getInputSourceFromDOC();
				nodes = (NodeList) xPath.evaluate(xPathExpression, inputSource,
						XPathConstants.NODESET);

				for (int i = 0; i < nodes.getLength(); i++) {
					System.out.println("found one");
					Node node = nodes.item(i);
					NodeList children = node.getChildNodes();
					for (int j = 0; j < children.getLength(); j++) {
						Node temp = children.item(j);
						if (temp.getNodeName() == "status") {
							statusList.add(temp.getTextContent());
							System.out.println("status: "
									+ temp.getTextContent());
						}
					}
					NamedNodeMap att = node.getAttributes();
					versionList.add(att.getNamedItem("version").getNodeValue());
					System.out.println("version: "
							+ att.getNamedItem("version").getNodeValue());

				}
			} catch (XPathExpressionException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			result.add(statusList);
			result.add(versionList);
			return result;
		} finally {
			r.unlock();
		}

	}
/*****************************************************************************
 * add new twitt into the DOM 
 * @param status new status
 */
	public void addNewStatus(String status) {
		w.lock();
		try {
			logger.info("writing data to the backend  server and the lock information is : "+rwl.toString());
			twitts = doc.getFirstChild();
			Element twitt = doc.createElement("twitt");

			NamedNodeMap twittsAttributes = twitts.getAttributes();
			Node maxVersion = twittsAttributes.getNamedItem("maxVersion");

			int maxV = Integer.parseInt(maxVersion.getNodeValue());

			twitt.setAttribute("version", Integer.toString(maxV + 1));
			maxVersion.setNodeValue(Integer.toString(maxV + 1));
			ArrayList<String> hashtags = utility.getHashflag(status);

			for (int i = 0; i < hashtags.size(); i++) {
				System.out.println("one tag " + hashtags.get(i).toString());
				Element hashtag = doc.createElement("hashtag");
				hashtag.setTextContent(hashtags.get(i).toString());
				twitt.appendChild(hashtag);
			}

			Element content = doc.createElement("status");
			content.setTextContent(status);

			twitt.appendChild(content);

			twitts.appendChild(twitt);
			System.out.println("after the post : " + utility.xmlToString(doc));
		} finally {
			w.unlock();
		}

	}

}
